home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Technotools
/
Technotools (Chestnut CD-ROM)(1993).ISO
/
lang_oth
/
ada1804
/
ada1804.hlp
Wrap
Text File
|
1990-09-21
|
123KB
|
3,488 lines
DRAFT
ADA STYLE GUIDE HANDBOOK
MIL-HDBK-1804
TABLE OF CONTENTS
1.0 SCOPE
1.1 Introduction
1.2 Scope
1.3 Goals
2.0 REFERENCED DOCUMENTS
2.1 Usage
2.2 Referenced Documents
3.0 DEFINITIONS
3.1 Introduction
3.2 Handbook
3.3 Standard
4.0 GENERAL REQUIREMENTS
5.0 SPECIFIC GUIDELINES
5.1 Introduction
5.2 Lexical Elements
5.3 Declarations and Types
5.4 Names and Expressions
5.5 Statements
5.6 Subprograms
5.7 Packages
5.8 Visibility
5.9 Tasks
5.10 Program Structure and Compilation Issues
5.11 Exceptions
5.12 Generic Units
5.13 Representation Clauses and Implementation-Dependent Features
5.14 Input-Output
1. SCOPE.
1.1 Introduction. Ada is a programming language of considerable expressive
power. ANSI/MIL-STD-1815A, Ada Programming Language, provides a thorough
definition of the language. However, it does not offer sufficient guidance on
the appropriate use of Ada's powerful features. This document was developed
to address "program style" issues and is presented as if the reader does not
have a mentor to explain all the whys and wherefores. It is for this reason
the document goes beyond styling techniques in some sections by showing
what is "good and proper" Ada constructs, denoting what to avoid, or why
certain constructs are used in a particular manner (proper software
engineering techniques are also "good style"). Besides, both facets are
needed in the code produced for the next person in line - the maintainer
and/or reuser.
1.2 Scope. Program source code serves two functions: to specify an
algorithm to be performed on a computer, and to communicate this algorithmic
design to other human readers. Program style relates to how well a program
meets the second function. It is a consistent manner of using the features of
a programming language to promote the maintainability, readability and
understandability of a program. This is a matter of the form of the final,
delivered, program source code, as opposed to the process of developing the
code.
1.3 Goals.
a. While some of what constitutes "good style" is subjective and
somewhat arbitrary, it is important that the style of a program be consistent
throughout the program. The primary goal of this guide is to promote such
consistent use of good style across large numbers of Ada programs. The whole
intent of "good style" is to increase the readability of these programs.
Therefore, these guidelines follow from general principles of program
readability and understandability.
b. The program should reflect the natural levels of abstraction in the
problem domain, so that the reader can reasonably comprehend each level
individually. Just as entites in real world applications have structure and
organization, so should Ada software systems. Furthermore, on a gobal level,
the overall software architecture should exhibit clarity and be readilly
comprehended. Ada coding style plays an important role in attainment of
these goals.
c. There are several features in Ada which are unfamiliar to many
programmers. There is thus a tendency to either underuse these features or to
use them inappropriately. A feature of Ada should generally not be ignored,
but neither should it be used in excess. The guidelines highlight the proper
use of Ada features. Keep in mind, there are features in the Ada language
that when used, are considered poor programming and/or software engineering
practices. Most of this knowledge has become apparent through the use of the
language. This guide's intent is to alert the reader of these practices; and
although they are functionally correct, should be avoided. (Words to that
effect will be used).
d. The textual format of a program should be pleasing to the eye and
promote the readability and understandability of the program. The format
should highlight the structure of a program and the role of a program as a
model of the problem domain. Just as the careful layout of a book can enhance
written communication, the careful layout of a program can enhance the
communication of algorithmic design to another human. The consistent use of
formatting style is especially important, because it allows readers to become
accustomed to the familiar layout of the program constructs. An automated
formatting program is particularly helpful, but even in the absence of such a
tool, much can be gained from a common format style.
2. REFERENCED DOCUMENTS
2.1 Usage.
a. This is not a static document. Other references need to be consulted
to provide a valid mix of ideas and "legal" practices when coding Ada. Where
applicable, conformity among the references should be obtained. Instances,
for example, where adopting an ISO or ARTEWG practice does not have a negative
impact, should be encouraged.
b. A listing of other references beside the one given in paragraph 2.2
will not be made. Other documents were referenced; however, to list them
could be construed as an endorsement of the document to the exclusion of
others. Better none than to miss one.
2.2 Reference documents. ANSI/MIL-STD-1815A, Ada Programming Language.
3. DEFINITIONS
3.1 Introduction. The definitions and acronyms provided in Ada Reference
Manual - ANSI/MIL-STD-1815A are applicable for use with this handbook. Since
this handbook is intended to be a companion document to the Ada Programming
Language manual, the definitions and acronyms are not restated here.
3.2 Handbook. The term "Handbook" (capitalization intended) refers to this
document, MIL-HDBK-1804, Ada Style Guide Handbook. This simplification is
used to improve readability.
3.3 Standard. The term "Standard" (capitalization intended) refers to
ANSI/MIL-STD-1815A, Ada Programming Language. This simplification is to
improve readability.
4. GENERAL REQUIREMENTS
NOT APPLICABLE
5. SPECIFIC GUIDELINES
5.1 Introduction. This section contains fourteen major subparagraphs (5.1
through 5.14) corresponding to the fourteen chapters of ANSI/MIL-STD-1815A,
Ada Programming Language. This provides a standard frame of reference for the
discussion of Ada features. Where appropriate, the guide includes examples
and justifications for various guidelines. Some of the major subparagraphs,
themselves, have an "Examples" subparagraph giving additional, longer examples
for the topic. When more than one example is shown, the order of preference
will be with the first example, then the second, etc.
5.1.1 Correlation. Each major subparagraph bears the same title as its
corresponding chapter in the Standard, e.g. Lexical elements is chapter 5.2
in this style guide. In the Standard, the basic definition starts in chapter
2.
5.1.2 Handbook Format.
a. Some constraints are implicity included in this Handbook. The reason
- the explanations are directed towards the Management Information Systems
environment moreso than the real-time, embedded systems environment. (Granted,
Ada was initially intended for the latter). Problems in "style" may occur
when information presented is slanted towards large memory features instead of
constrained, limited memory systems. (Block statements come to mind as an
example). Therefore, for real-time programmers/maintainers, comments starting
with "REAL_TIME" will be included and are directed to their use.
b. Non-numbered paragraphs are editorial comments included to provide
further clarifications. Also, information in this category and begun with
the notation of "NOTE:", are of specific interest and special attention should
be paid to them.
5.1.3 Non-compliance. It should be noted that some compilers contain
restrictions that may not allow compliance with all of the guidelines contained
in Section 5.2 and beyond. An example would be the inability of a system to
allow the use of the underscore. The Standard, chapter 2.1, "Charater Set",
includes the underscore as an allowable special character. Obviously, a
physical impasse is apparent, and as such, should be documented up front in
the program. This is a basic premise of this guide - if you cannot comply
with the guidance, information stating such should appear in the program.
5.1.4 Basic Style Format.
a. The emphasis in style is to have distinct, recognizable parts. That
is, information to be defined should be on one side, information doing the
defining on the other; or, on one line, with the defining components on the
following line(s). The right-left concept comes more into play with the usage
of the assignment operator (see 5.2.2.7). The one-line-next-line concept will
cover the majority of style formating use.
b. The reasons for promoting the one-line-next-line style are reada-
bility and understandability. From the first character on a line to its
associated semicolon, some information contained between the two must be
dominant, some must be subordinate. Whether all the information is on one
line or many does not matter syntactically, it does for readability and
understandability. Therefore, to promote easier end of line comments,
maintainability, as well as readability and understandability, the following
basic style is recommended (exceptions are delineated in other chapter 5
subchapters):
1) Reserved words denoting various elements of an Ada program and
their associated names should be on one line, their subordinate information
on the succeeding line(s), e.g.
procedure <procedure name>
[<parameter specification>];
function <function name>
[<parameter specification>];
2) Type declarations or other reserve word phrases ending with a verb
should be on one line, its subordinate information on the succeeding line(s),
e.g.
type (identifier> is
<type definition>;
task type <task name> is
<entry statement>;
end <task name>;
task body <body name> is separate;
<loop name>:
while <expression> loop
<statement>
end loop <loop name>;
3) Objects, strings and similar declarations where the colon
differentiates the identifier from the definition, should be "muliti-lined"
by placing the identifier on one line and the colon and the definition on
the subsequent line(s), e.g.
<object name>
: <definition>;
4) Information being protrayed using the assignment operator should
use the left-right concept, e.g.
identifier := <expression> [<operator starting continued expression>
<continued expression>];
The expression(s) are continued by leaving an operator on the previous line
and continuing the expression directly under the start of the expression on
the line above (see 5.2.2.7 for an example).
5) The basic formats are not used if multiple occurrences of the same
form are used in a subordinate role (see 5.3.8.4 b), e.g.
package <package name> is
procedure <procedure name> [<passing parameters>];
procedure <procedure name> [<passing parameters>];
function <function name> [<passing parameters>];
function <function name> [<passing parameters>];
private
end <package name>;
NOTE: if any of the procedures or functions in the above example require
continuation to the next line, all of the subordinate information should be
continued in the same manner (see 5.2.2.7).
5.2 Lexical elements.
5.2.1 The package STANDARD. Language words with predefined meanings in the
package STANDARD should not be redefined.
5.2.2 Formatting of lexical elements.
5.2.2.1 Indentation. The preferred indentation level is two spaces.
Exceptions are in some cases continuations.
NOTE: The emphasis is more on the indention being consistent throughout the
program than the exact number of spaces. The key word is consistent!
5.2.2.2 Character set. Full use should be made of the ISO character set
where available. Alternate character replacements should only be used when
the corresponding graphical symbols are not available or are reserved for
other uses (see chapter 2.10, the Standard).
5.2.2.3 Upper/lower case.
NOTE: This area is one of just a few that cause the most consternation among
Ada coders because rationale can be provided for numerous formating methods.
All things considered, the following was thought best ...
a. Reserved words and attributes should appear in lower case, e.g.
package body Choice is separate;
type REAL is digits 8;
b. All identifiers except type, enumeration value and attribute
identifiers should be in mixed upper and lower case. The first letter of each
word in the identifier should be in upper case with other characters in lower
case, unless a word is normally written in all upper case, as are acronyms.
Display_Device
Number_Of_User Names
Get_FHST_Data
Package_Name
List'first -- "first" is an attribute designator (preceeded by an
-- apostrophe), not a reserve word.
c. Type names, subtype names and enumeration literals should appear in
all upper case.
type COLOR is (WHITE, RED, YELLOW, GREEN, BLUE, BROWN, BLACK);
subtype RAINBOW is COLOR range RED .. BLUE;
type ARMED_FORCE is (ARMY, AIR_FORCE, NAVY, MARINES);
5.2.2.4 Identifiers.
a. Identifier names should be meaningful and easily distinguishable from
each other, including loop parameters, array indices, and common mathematical
variables. Names of objects and types should be nouns or noun phrases; names
of procedures should be verbs or verb phrases; and, names of enumeration
literals should be adjectives if they are meant to represent properties.
b. Distinct words in identifiers should always be separated by
underscores.
c. The use of abbreviations in identifiers should be avoided. When used,
an abbreviation should be significantly shorter than the word it abbreviates,
and its meaning should be clear. The same abbreviations should be used
consistently throughout a project, precedence is given to sanctioned
standard data elements and codes or existing functional data dictionaries
abbreviations, e.g. MGT for management; RQD for required.
d. Acronyms should be avoided if at all possible.
5.2.2.5 Spaces. Single spaces should be used consistently between lexical
elements to enhance readability.
5.2.2.6 Blank lines.
a. Blank lines should be used to group logically related lines of text.
A careful use of blank lines can greatly enhance readability by making the
logical structure of a sequence of lines more textually obvious. However,
the overuse of blank lines (e.g., "double spacing") defeats the very purpose
of grouping and can actually reduce readability. Blank lines should thus
always be used with grouping in mind and not just to increase white space.
b. A blank line should always preceed and follow a construct whose last
line is not at the same indentation level as its first line.
-- preceeded by a blank line
type COMPLEX is
record
Real : REAL := 0.0;
Imaginary : REAL := 0.0;
end record;
-- Followed by a blank line
5.2.2.7 Continuation of statements.
a. tatements extending over multiple lines should be broken after reserved
words, operators, delimeters, commas, or if a distinct entity can be
separated. The one-line-next-line concept should be utilized as discussed
in the basic formats section, e.g.
package Information is
procedure Information_Left_Over
(Return_File : in RETURN_FILE_TYP);
function Check_Last_Element
(Return_File : in RETURN_FILE_TYP)
return BOOLEAN;
end Information;
For assignments, use the left-right concept, e.g.
Corrected-Value := ((1 + Sensor_Scale) * Raw_Value) +
(Distortion_Factor * Distortion_Value) -
Sensor_Bias;
NOTE: Redundant parenthesis are used (see 5.4.2.2).
b. Long strings extending over more than one line should be broken up at
natural boundaries, appropriate to the meaning of the contents of the string,
if any.
"This is a rather long string, so it is likely that "
& "it will extend over more than one line"
NOTE: Do not confuse continuation with basic format (see 5.1.4).
5.2.2.8 Comments.
a. Comments should be used to add information for the reader or to high-
light sections of code, and should not merely paraphrase the code.
b. Comments should begin with the "--" aligned with the indentation level
of the code that they describe, or to the right of the code, aligned with
other such comments.
-- Check if the user has special authorization
if Authority = SPECIAL then
Display_Special_Menu; -- All operations are allowed
else
Display_Normal_Menu; -- Only normal operations allowed
end if;
c. Refrain from including any character after the "--", even though
the Standard gives an example of numerous hyphens in a row. Several formal
specification languages (commercial-off-the-shelf tools) use the
--<special symbol> in development and production of automated specification
documents.
5.2.2.9 Length of a Line. The preferred length of a line is 72 characters.
Various restrictions begin to occur, window boarders, etc., if the line
extends further.
5.3 Declarations and types.
NOTE: Be advised and understand the limitations imposed with predefined
numeric types (FLOAT, INTEGER, etc.) This is due to their implementation
dependent precision.
5.3.1 Constants.
a. An object should be declared constant if its value is intended not to
change.
Declaring an object to be constant clearly signals both the human reader and
the compiler the intention that its value will not change. This not only
increases readability, it also increases reliability because the compiler will
detect any attempt to tamper with the object. Also, it can result in some
decrease in executable size and better run time efficiency.
b. Defining a constant object is preferable to using a numeric literal
or expression with constant value, as long as the constant object has an
intrinsic conceptual meaning. Constants should not be used to avoid
enumeration type.
There is no use in defining a constant object when a numeric literal is
obviously more appropriate, for example: using "One" instead of "1." However,
the use of constant objects with intrinsic meaning (such as "Buffer_Size" or
"Field_Of_View") can greatly increase the readability of code. Further, the
code is more maintainable since a change in a value will be localized to the
constant declaration.
c. A named number (i.e., a constant object with type universal-integer
or universal-real) should be used only for values that are truly "universal"
and "typeless." Other numeric constants should be declared with an explicit
type.
Constants, such as "Pi", and cardinal integers (e.g., a "number of things")
should be named numbers. Note also that declaring a constant in terms of a
predefined numeric type (INTEGER, FLOAT, etc.) in most cases has no advantage
over a named number since these predefined types provide only range and
accuracy constraints and not additional conceptual meaning. In fact, since
the range and accuracy of predefined numeric types is implementation-defined,
portability can be increased by using named numbers, in those cases where a
constant of a user-defined type is not more appropriate.
Pi
: constant := 3.141_592_65;
Number_Of Sensors -- This is a named number
: constant := 4;
Main_Sensor_Number
: constant SENSOR_INDEX := 2;
REAL_TIME: Multiplication or division of a fixed point type by an integer
results in an object of that fixed point type (as opposed to multiplication
of an integer by an fixed point type, which results in an integer). Using
a constant of universal fixed rather than a specified fixed point type may
force the introduction of an explicit type conversion, possible reducing the
efficiency of the multiplication.
5.3.2 Types and Subtypes.
5.3.2.1 Types. Separate types should be used for values that belong to
logically independent declarations, and for distinct concepts.
type X_COORDINATE is
range 1 .. 640;
type Y_COORDINATE is
range 1 .. 480;
type PIXEL_VALUE is
range 0 .. 255;
type IMAGE_GRID is
array (X_COORDINATE, Y_COORDINATE) of PIXEL_VALUE;
A data type characterizes a set of values and a set of operations applicable
to objects of the type. In the above example, each coordinate has a type
because coordinates are independent entities. Explicitly declaring these
types makes the concepts more obvious to a human reader and also allows the
detection of mistakes such as:
X_Coord : X_COORDINATE := 1;
Y_Coord : Y_COORDINATE := 1;
Pixel : PIXEL_VALUE := 0;
Image : IMAGE_GRID;
Image (Y_Coord, X_Coord) := Pixel; -- Should be "(X_Coord, Y_Coord)"
The drawback of this kind of typing is that the following construct is
illegal:
if X_Coord = Y_Coord then -- ILLEGAL since X_Coord and Y_Coord
... -- have different types
A type conversion must be used:
if X_Coord = X_COORDINATE (Y_Coord) then
...
Note that, depending on context (and compiler quality), there may or may not
be some run time penalty associated with type conversion (e.g., testing of
range constraints).
5.3.2.2 Subtypes. For understandability and maintainability, all types and
subtypes should be grouped together. This ensures the type being subtyped,
if it is user defined, already exists in the program. Subtypes should be
used if the problem consists of subsets of the base type, or subsets of the
subsets, e.g.
type NON_NEGATIVE is range 0 .. INTEGER'last;
subtype ORDER_SIZE is NON_NEGATIVE range 0 .. 1_000_000;
subtype SMALL_ORDER is ORDER_SIZE range 0 .. 50;
subtype BIG_ORDER is ORDER_SIZE range 51 .. 1_000_000;
5.3.3 Enumeration types. An enumeration type should always be used in
preference to an integer type, unless the logical nature of the concept to be
modeled demands the other.
For example the type:
type DEVICE_MODE is
(READ_ONLY, WRITE_ONLY, READ_WRITE);
is preferable to encoding DEVICE_MODE as an integer 0, 1 or 2.
5.3.4 "Point" types.
5.3.4.1 Floating point types.
a. To enhance portability, always specify the range, constraints and
accuracy of a floating point type.
b. The precision for the predefined floating types (FLOAT, etc.) is
implementation-dependent, though all implementations should provide at least 6
decimal digits of accuracy. Explicitly declaring floating point accuracy
should yield more reliable, efficient and portable code.
5.3.4.2 Fixed point types. Fixed point types should be expressed with the
constraints and boundaries appropriate for the identifier. Explicitly
stating these parameters, as noted with floating point types, enhances the
portability because the type will be code dependent instead of machine
dependent.
5.3.5 Record types.
a. A record type is preferred when each component can be sensibly
named, the components do not need to be dynamically indexed, and the
combination of components is needed to provide full meaning to the type, e.g.
type COMPLEX is
record
Real : REAL := 0.0;
Imaginary : REAL := 0.0;
end record;
"COMPLEX" needs both the Real and the Imaginary part to have meaning.
Whereas, arrays are useful when one or more component(s) are primary over
others, or access is needed via indexing, e.g.
type WORK_DAY is (MON, TUE, WED, THU, FRI, SAT, SUN, HOL);
type DOLLARS is array (5 .. 7) of INTEGER;
type OVERTIME is array (WORK_DAY) of DOLLARS;
Once the dollars are "loaded" into OVERTIME, computations, etc. will be
easier than having to scan records for the appropriate OVERTIME.
b. Overcomplicated record structures should be avoided by grouping
related data into subrecord types.
type COORDINATE is
record
Row : INTEGER range 1 .. 20;
Column : INTEGER range 1 .. 30;
end record;
type WINDOW is
record
Top_Left : COORDINATE;
Bottom_Right : COORDINATE;
end record;
c. Discriminants. Discriminants should be used whenever the object
has closely related variances which would more easily be understood tied to
a single data type, e.g.
type BUFFER (Size : BUFFER_SIZE := 100) is
record
Position : BUFFER_SIZE := 0;
Value : STRING (1 .. Size);
end record;
A record component may or may not depend on the discriminant. In the above
example, the component Value depends on the discriminant Size for its upper
boundary.
d. Enumeration types should be used for discriminants of record
variants whenever possible. A discriminant should generally have a default
initialization only if the discriminant value is intended to change over the
lifetime of an object.
e. Variant parts. One of the strong features of Ada is the use of
variants in defining alternate lists of components. This is good software
engineering practice because all possible uses of a discriminant are defined
and in one location, with the constraints being specified in the variable or
a subtype declaration, e.g. (from the Standard)
type DEVICE is (PRINTER, DISK, DRUM);
type STATE is (OPEN, CLOSED);
type PERIPHERAL (Unit : DEVICE := DISK) is
record
Status : STATE;
case Unit is
when PRINTER => Line_Count : LINE_RANGE;
when others => Cylinder : CYLINDER_INDEX;
Track : TRACK_NUMBER;
end case;
end record;
In this example, a general record type is made for the three types of
peripheral units. From there, subtypes can be created of specific
peripheral units, e.g.
subtype DRUM_UNIT is PERIPHERAL (DRUM);
If a constraint was not stated in a subtype or variable declaration, the
compiler would assume the default of DISK.
5.3.6 Access types.
a. Generally, access types should not be used when static types and
stack allocation would be sufficient.
b. Generally access types should be used only when it is necessary
to have data structures with dynamic pointers or to dynamically create
objects. However, access types may be needed for static objects if this leads
to a more consistent programming style (e.g., so that similar static and
dynamic objects are treated identically). For example, if linked lists are
used in a program, there may be some lists which are constant, but which are
still implemented as linked lists using access types. This would allow, for
example, passing these constant lists to subprograms which also handle dynamic
lists.
REAL_TIME: There are certain instances where it is desirable to use access
types to address static objects in embedded real-time applications. Certain
Ada compilers support only limited types of parameters when pragma INTERFACE
is used (to assembly language, C, or some other language selected for
efficiency or interface requirements), and access types are usually one of
those supported when other static types, such as record structures, are not.
5.3.7 Object declarations.
a. A single object declaration should be used to gather objects of the
same type, e.g.
Table Size,
Table Index,
Current Entry
: TABLE_RANGE;
b. An object should not be declared using a unnamed constrained array
definition.
The unnamed array definition is the only case in Ada where an object can be
declared to be of a type which does not have a name. Instead, the array type
should be named in an array type declaration, and that name used in the object
declaration, even if there is only one object declared of that type.
type POOL_TYP is
array (POOL_RANGE) of CHARACTER;
POOL
: POOL_TYP;
c. Objects should generally be initialized. Where possible, objects
should always be initialized by their declaration, rather than in later code.
Is_Found
: BOOLEAN := FALSE;
5.3.8 Formatting of declarations and types.
5.3.8.1 Commenting.
a. Type declarations (or groups of declarations) should be commented to
indicate what is being defined, if that is not obvious from the type
declaration itself.
type VELOCITY is -- Inertial velocity relative to the Earth
array (1 .. 3) of FLOAT; -- Three test samples needed for comparison
b. Object declarations should always be commented if the object
definition is unclear from the object and type identifiers alone. Note
that those properties of an object obtained from its type should not be
repeated in comments on the object declaration.
Spacecraft_Velocity -- Spacecraft orbital velocity, assuming a
: VELOCITY; -- circular orbit
5.3.8.2 Indentation. All declarations in a single declaration part should
begin with the same indentation level.
5.3.8.3 Type definitions.
a. Array type definitions should have one of the following formats:
type <type name> is
array <index definition> of <subtype indication>;
type <type name> is
array <index definition>
of <subtype indication>;
b. Record type definitions should have one of the following formats:
type <type name> is
record
<component declaration>
<component declaration>
end record;
type <type name>
( <discriminant declaration>;
<discriminant declaration> ) is
record
<component declaration>
case <discriminant name> is
when <choices> =>
<component declaration>
<component declaration>
end case;
end record;
c. All <component declarations> and <discriminant declarations> should be
formatted like object declarations (see paragraph 5.3.8.4).
d. Other type definitions should be formatted as follows:
type <type name> is
<type definition>;
subtype <type name> is
<subtype indication>;
e. Long enumeration type definitions should be formatted into easily
readable columns.
5.3.8.4 Formatting of object declarations.
a. Object declarations should have one of the following formats. The
preferred formats are:
<object name>
: <subtype indication> := <expression>;
b. When the subtype indication and/or expression is long, or when comments
are needed, it may be necessary to futher continue the declaration as follows:
<object name>
: <subtype indication> -- narrative explanation of subtype
:= <expression>;
c. Declarations containing short identifiers may also be formatted all on
one line, and is the perferred method when used as subordinate or supportive
declarations, e.g.
<object name> : <subtype indication> := <expression>;
In this case, all such declarations textually grouped together or appearing as
components in a single record definition or in a single parameter list should
have their ":" and ":=" symbols aligned.
5.3.9 Examples for declarations and types.
5.3.9.1 Example 3X1.
type SENSOR_ARRAY is
array (NATURAL range <>) of SENSOR;
UARS_Sensors -- Sensor configuration for the
: SENSOR_ARRAY (1 .. Num_Sensors); -- UARS control system
5.3.9.2 Example 3X2.
type JULIAN_DATE_NUMERIC is
record
Two_Digit_Year : INTEGER range 0 .. 99;
Numeric_Day : INTEGER range 1 .. 366;
end record;
5.3.9.3 Example 3X3.
type DEVICE is
(PRINTER, DISK, DRUM);
type STATE is -- Operational state of a
(OPEN, CLOSED); -- device.
type PERIPHERAL
(Unit : DEVICE := DISK) is
record
Status : STATE;
case Unit is
when PRINTER =>
Average_Lines_Per_Page : INTEGER range 1 .. Page_Size;
when DISK | DRUM =>
Cylinder : CYLINDER_INDEX;
Track : TRACK__NUMBER;
end case;
end record;
5.4 Names and expressions
5.4.1 Aggregates.
a. Aggregates are preferable to individually setting all or most of the
components of an array or record.
b. Named aggregates should be used where possible.
5.4.2 Static expressions. Being able to differentiate between static and
universal expressions and their respective uses will assist in understanding
different characteristics of software engineering practices, i.e. to
maximize accuracy, static expressions should be used. The detail can be
provided to the depth desired, e.g.
Pi : constant := 3.141_592;
Two_Pi : constant := 6.283_185;
Whereas, understandability and readability are increased using universal
expressions, e.g.
Half_Pi : constant := Pi / 2.0;
Deg_To_Rad : constant := Half_Pi / 90.0;
Rad_To_Deg : constant := 1.0 / Deg_To_Rad;
NOTE: Two_Pi could have been gained by the expression Pi * 2.0; however,
if accuracy is needed to six decimal places, the static expression must be
used or the original Pi would have to show additional decimal places. (The
seventh decimal value for Pi is a 6; therefore, doubling Pi, the sixth
decimal is a 5, not a 4 as would be obtained).
5.4.3 Short-circuit control.
a. Short-circuit control forms should generally be used only to avoid
evaluation of an undefined or illegal expression. Short circuit operators
should not be used to optimize execution.
(N /= 0) and then (Total/N > Limit)
(Index = 0) or else User(Index).Not_Available
b. The short-circuit control forms should be used to signal to a human
reader that the correctness of the second condition depends on the results of
the first. They should not be used for micro-efficiency reasons, concerns
better handled by an optimizing compiler. If efficiency considerations are
substantially important, "if" statements should be used instead of the short-
circuit forms with functions used to avoid repeated code, if necessary.
REAL_TIME: Memory considerations are a concern; therefore, the opposite
should be true, i.e. short-circuits should be used over "if" statements.
5.4.4 Type qualification.
a. An explicit type conversion should not be used if a type qualified
expression is meant.
Good : LONG_FLOAT'(3.141_59)
Bad : LONG_FLOAT (3.141_59)
A qualified expression is used to state explicitly the type, and possibly sub-
type, of a value. A type conversion, however, results in the dynamic
conversion of a value to a target type. Sometimes a type conversion can be
used to serve the purpose of a type qualification. However, if the operand is
already of the desired base type, a conversion is not really necessary and a
qualification should be used instead.
b. Type qualification should be avoided if possible, especially when
the type of an enumeration literal or aggregate is not known from context.
For example:
type COLOR is
(BLACK, RED, GREEN, BLUE, WHITE);
type LIGHT is
(RED, YELLOW, GREEN);
procedure Set
( Color_Code : in COLOR );
procedure Set
( Color-_Code : in LIGHT );
...
Set (COLOR'(RED)); -- Type qualification must be used here to
Set (LIGHT'(RED)); -- resolve the overloading of Set and RED
It would be better in this case to rename one of the Set procedures
or to at least give them different parameter names so the overloading
could be resolved using name notation.
5.4.5 Formatting of names and expressions.
5.4.5.1 Names.
a. The name for a type should be a common noun indicating the class of
the objects it contains.
DEVICE
AUTHORITY_LEVEL
USER_NAME
PHONE_LIST
A type name should end with the suffix "_TYP" if convolution will develop
among other types (enumeration, etc.) with the same name.
EMPLOYEE_TYP
SCHEDULE_TABLE_TYP
COLOR_TYP
NOTE: The POSIX/Ada committee has recommended the suffix "_TYPE" not be used
because of various problems, and it will not be used in the Ada/POSIX
interface standard.
b. The names of non-BOOLEAN valued objects should be nouns, preferably
more precise than the names of types.
Current_User
: USER_NAME;
Output_Device
: DEVICE;
Schedule_Table
: SCHEDULE_TABLE_TYP;
New_Employee
: EMPLOYEE_TYP;
BOOLEAN valued objects should have predicate-clause (e.g., "Is Open") or
adjective names.
User_Is_Available
List_Empty
Done
Not_Ready
Is_Waiting
5.4.5.2 Parentheses. Syntactically redundant parentheses should generally be
used to enhance the readability of expressions, especially by indicating the
order of evaluation. This is true even for single expressions where operators
of different precedence levels are present.
For example:
Variance := (Roll_Error ** 2) + ((Yaw_Error ** 2) / 2);
5.4.5.3 Aggregates format.
a. When longer than two components, or whenever readability is
improved, named aggregates should be formatted as indicated below, with one
association per line and the "=>" arrows aligned.
Output_Device := ( Device => DISK,
Status => CLOSED,
Cylinder => 1,
Track => Startup_Track_Num );
b. Aggregates for multidemensional arrays may be formatted in a
tabular fashion to enhance readability.
5.5 Statements.
5.5.1 Slice Statements. Array slice assignments should be used rather than
loops to copy all or part of an array. This is more readable and less error
prone, especially in the case of slices with overlapping ranges.
Client_List (Last_Client .. Number_Of_Clients) :=
New_Clients (1 .. Num_New_Clients)
NOTE: on format - array elements should stay with the array name, which is
why a left-right format was not used in this instance (line too long).
on syntax - array slice assignments work properly only if both arrays
are of the same type.
5.5.2 If statements. An "if" statement should not be used to create the
effect of a "case" statement controlled by the value of an enumeration type
other than BOOLEAN.
5.5.3 Case statements.
a. A "case" statement should be controlled by an enumeration type, not
by a BOOLEAN value.
b. When possible, the explicit listing of all choices on a "case"
statement is preferable to the use of an "others" clause.
This makes it easier for a human reader to see that the proper actions are
being taken in all cases. Further, if the enumeration type of the control
expression is modified, the compiler will indicate overlooked alternatives.
However, there are cases when an "others" clause makes sense. For example, if
the control expression is of type character, then it is usually best to use an
"others" clause to handle the "undesired characters" case.
5.5.4 Block statements.
a. Blocks should be used cautiously to introduce local declarations or to
define a local exception handler. Resources for data local to the block are
allocated only when the block is entered (elaborated). This is a fundamental
difference between procedures and blocks. A procedure is callable by any
number of other program units while a block is limited in scope to its
immediate lexical level and cannot be called.
To some extent, a block can be thought of as a procedure which is hard coded
in-line. However, a procedure call contributes to readability precisely by
not having its source code in-line (providing a "functional abstraction").
Therefore, blocks should always be used cautiously and only for specific
purposes. Thought should always be given to using a procedure call instead of
a block to improve readability.
b. Declarations of objects used only within a block should be nested
within the block.
REAL_TIME: Procedure calls require substanial execution time overhead. A
potential run-time efficiency advantage of blocks is memory savings. If a
declare statement is used in the block, the resources for data which are local
to the block will only be allocated when the block is entered. Additionally,
some compilers may deallocate these memory resources after the block is exited.
5.5.5 Exit statements. "Exit" statements should be used cautiously, and
only when they significantly enhance the readability of the code. If used,
the exit statement should use the name of the loop it is exiting.
It is often more readable to use exit than to try to add BOOLEAN variables to
a "while" loop condition to simulate exits form the middle of a loop.
However, it can be difficult to understand a program where loops can be
exited from multiple places. It is best to limit the use of "exit" statements
to one per loop, if possible, and it is generally more readable to use "exit
when." Use "if...then...exit; end if" when "last wishes" processing is needed.
REAL_TIME: "Exit"s should not be used because they can greatly increase run
time if the code at the exit address has to be paged into working space.
5.5.6 Return statements. It is preferable to minimize the number of return
points from a subprogram, as long as this does not distract from the natural
structure or readability of the subprogram.
5.5.7 Goto statements. It is deemed as poor programming practice to use
"goto" statements.
Use of the "goto" makes the textual structure of code less reflective of its
logical structure. Possible uses of the "goto" statement can always be
handled by other constructs in Ada, such as a procedure call or via an exit.
5.5.8 Formatting of statements.
5.5.8.1 Statement sequences. Blank lines should be used liberally to break
sequences of statements into short, meaningful groups (see paragraph 5.2.2.6).
Put_Line ("Welcome to the Electronic Message System");
Logon_User (Current User);
User_Directory.Lookup
( User_Name => Current_User
Authority => User_Authority );
if User_Authority = SPECIAL then
Put_Line ("** You have SPECIAL authorization **");
end if;
5.5.8.2 If statement format.
a. "If" statements should have the following format:
if <condition> then
<statement>
<statement>
elsif <condition> then
<statement>
<statement>
else
<statement>
<statement>
end if;
b. Multiple conditions in an "if" clause should be grouped together,
placed on appropriate lines, and aligned so as to enhance clarity.
5.5.8.3 Case statement format. "Case" statements should have the following
format:
case <expression> is
when <choices> => <statement>
when others => <statement>
end case;
case <expression> is
when <choices> =>
<statement>
<statement>
when others =>
<statement>
<statement>
end case;
5.5.8.4 Loop statement format.
a. "Loop" statements should have one of the following formats:
<loop name>:
<iteration scheme> loop
<statement>
<statement>
end loop <loop name>;
b. A loop should preferably have a loop identifier.
5.5.8.5 Block statement format.
a. Block statements should have the following format:
<block name>:
declare
<declaration>
<declaration>
begin
<statement>
<statement>
exception
when <exceptions> =>
<statement>
<statement>
end <block name>;
b. Blocks should always have a block identifier.
5.5.9 Examples for statements.
See also examples 5.9.14.2, 5.9.14.3, 5.9.14.4, 5.14.5.1, and 5.14.5.2.
5.5.9.1 Example 5X1.
if Security_Level = 0 then -- appropriate comments
Message_Classification :=UNCLASSIFIED; -- should be placed here
elsif Security_Level > User_Clearance then -- stating what's going
Message_Classification := PROTECTED; -- on
else
Message_Classification := Classification (Security_Level);
end if;
5.5.9.2 Example 5X2.
case Sensor is
when ELEVATION => Record_Elevation (Sensor_Value); -- <comments>
when AZIMUTH => Record_Azimuth (Sensor_Value);
when DISTANCE => Record Distance (Sensor_Value);
end case;
5.5.9.3 Example 5X3.
Read_File:
while not Text_IO.End_Of_File (File 1) loop
Text_IO.Get (File1, Next_Record); -- <comments>
Store_Record (Next_Record);
end loop Read_File;
Compute_Total_Taxes:
while Next /= Head loop
Total_Taxes := Total_Taxes + Next.Pay_Period Deductions;
Next := Next.Successor;
end loop Compute_Total_Taxes;
Merge Files:
for N in 1 .. Max_Num_Files loop
Get_Items:
for J in 1 .. Max_Num_Items loop
Get_New_Item (New_Item);
Merge_Item (New_Item, Storage_File);
exit Merge_Files when New _Item = Terminal Item;
end loop Get_Items;
end loop Merge_Files;
5.5.9.4 Example 5X4.
Swap_Integers:
declare
Temp : constant INTEGER := U;
begin -- Swap_Integers;
U := V;
V := Temp;
end Swap_Integers;
Check_Entry:
begin
Int IO.Get (Value);
Update (Value);
exception
when Data Error =>
Text_IO.New_Line;
Text_IO.Put_Line ("Value entry error.");
Entry_Error_Flag := TRUE;
end Check_Entry;
5.6 Subprograms.
5.6.1 Cohesion. A subprogram should perform a single, conceptual action
(i.e., should be "functionally cohesive").
The use of a subprogram increases readability by hiding the details of how an
action is performed and giving it a descriptive name. A subprogram should
perform only a single conceptual action so that its use can be understood as
independently as possible from its implementation details and it can be given
a self-documenting name. Note that simply shortening a program by placing
"repeated code" into subprograms must be considered a secondary goal. Thus it
is quite acceptable to have subprograms which are only called at one place, so
long as those programs define cohesive actions.
NOTE: Explicit parameter passing is the best way to build interfaces to
subprograms. This method keeps the affected variables clearly defined and
visible. Intensional use of side effects is considered poor programming
practice and should be used on a limited basis. If used, side effects should
be extensively commented ... where they are taking place and where they are
affected.
5.6.2 Parameters.
a. Subprograms with equivalent parameters should generally declare each
parameter in the same position with the same identifier.
b. Parameters with default expressions should be used only when they have
very well known default values and/or they are defaulted most of the time,
with the default being over-ridden only in special circumstances. Great care
should be taken in using defaults, insuring the "mixing" of default and named
parameters does not disrupt the understandability and readability of the code.
c. Parameters with default expressions should generally be placed at the
end of the parameter list, so that they may be omitted if desired in calls
using positional notation.
d. As noted in the section for naming (5.2.2.4), parameters should
follow a similar schema, chosen to promote readability of the subprogram
calls, and where possible, should reflect the mode of the parameter.
5.6.3 Recursion. A recursive subprogram should generally be used only if
it is conceptually simpler for a given problem than a corresponding iterative
subprogram.
Many people have difficulty in understanding a program which uses recursion
extensively. However, there are many cases where a recursive solution is
considerably simpler and clearer than an iterative one. This is especially
true, for example, for traversing complicated data structures such as tree and
graph structures.
5.6.4 Functions. A subprogram without side-effects returning a single
value should generally be written as a function. Since functions can be called
from within expressions, there is more freedom in how a function can be used.
For example, if a function is to be called only once within some other
subprogram, it can be used to initialize a constant object.
procedure Process_Sensor Data is
Main_Sensor_Data
: constant SENSOR_DATA := Read_Sensor (Main_Sensor_Index);
begin
...
However, if this sort of freedom is specifically not desired, or if the
subprogram has side effects, then use of a procedure should be considered
instead of a function, even if the subprogram returns only a single value.
5.6.5 Overloading.
a. Overloading of subprograms should not be done. Use a meaningful
function name instead. Possible (a weak possible) exceptions are the
following cases:
(1) Widely used utility subprograms which perform identical or very
similar actions on arguments of different types (e.g., square-root of integer
and real arguments).
(2) Overloading of operator symbols.
Note that this is not meant to cover subprograms with identical names in
different packages, unless both subprograms are visible through "use" clauses
for their packages.
b. Operator symbols should be overloaded only when the new operator
definitions comply closely with the traditional meaning of the operator (e.g.,
"+" for vector addition).
5.6.6 Formatting of subprograms.
5.6.6.1 Subprogram names.
a. Except as indicated below, a subprogram name should be an imperative
or interrogatory verb phrase describing its action.
Action_Complete
Obtain_Next_Token
lncrement_Line_Counter
Create_New Group
b. Non-BOOLEAN valued function names may also be noun phrases.
Top_Of_Stack
X_Component
Successor
Sensor_Reading
c. BOOLEAN valued functions should have predicate-clause names.
Stack_Is_Empty
Last_Item_Check
Device_Not_Ready
5.6.6.2 Subprogram header. Each subprogram specification, body or stub
should be preceded by a header comment block containing at least the
subprogram name and the indication SPEC, BODY, SPEC & BODY, STUB or SUBUNIT.
-- ............................
-- . .
-- . Obtain_Next_Token . SPEC
-- . .
-- ............................
5.6.6.3 Subprogram declarations.
a. Procedure declarations should have the following format:
-- <subprogrammer header>
procedure <procedure identifier>
( <parameter specification>;
<parameter specification> );
-- <documentary comments>
Each <parameter specification> should be formatted like an object declaration
(see paragraph 5.3.8.4). The documentary comments should follow guideline d.,
below.
b. Function declaration should have the following format:
-- <subprogrammer header>
function <function designator>
( <parameter specification>;
<parameter specification> )
return <type mark>;
-- <documentary comments> Each <parameter specification> should be
formatted like an object declaration (see paragraph 5.3.8.4). The
<documentary comments> should follow guideline d., below.
c. Parameter mode indications should always be used in procedure
specifications. In a function specification, mode indications should either
be used for all of the parameters or none of the parameters.
d. Subprogram declarations should be followed by at least the following
documentation:
-- PURPOSE <--headings can be upper or lower case
-- A description of all purposes and functions of the subprogram.
--
-- EXCEPTIONS
-- A list of all exceptions which may propagate out of the subprogram,and
-- a description of when each would be raised.
--
-- NOTES
-- Additional comments on the use of the subprogram, references, etc.
The "Exceptions" and "Notes" headings should be included even if these
sections are empty. An empty section may be indicated by placing the
annotation "(none)" after the appropriate header. Only in the case that the
subprogram declaration is a compilation unit, the following section should be
added to the documentation:
-- MODIFICATIONS
-- A list of modifications made to the subprogram DECLARATION.
-- Each entry in the list should include the date of the change,
-- the name of the person who made the change and a description
-- of the modification. The first entry in the list should
-- always be the initial coding of the subprogram declaration.
5.6.6.4 Subprogram bodies and stubs.
a. Subprogram bodies should have the following format if not standalone:
separate (<parent name>)
<subprogram specification> is
-- <documentary comments>
<declaration>
<declaration>
begin -- <subprogram name>
<statement>
<statement>
exception
when <exceptions> =>
<statement>
end <subprogram name>;
The <subprogram specification> should be formatted as in a subprogram
declaration (see paragraph 5.6.6.3). The <documentary comments> should follow
guide- line b., below. Note that the "end" of a subprogram should always
include the subprogram name.
b. Subprogram bodies should have AT LEAST the following documentation
placed immediately after the subprogram header:
-- NOTES
-- Comments on the design, implementation and use of the
-- subprogram.
The "NOTES" heading should be included even if the section is empty. An empty
section may be indicated by the comment "Notes (none)." Only in the case of a
subprogram body which is a separate compilation unit from its specification,
the following section should be added to the documentation.
-- MODIFICATIONS
-- A list of modifications made to the subprogram BODY.
-- Each entry in the list should include the date of the change,
-- the name of the person who made the change and a description
-- of the modification. The description should identify exactly
-- where in the compilation unit that the change was made. The
-- first entry in the list should always be the initial coding
-- of the subprogram body.
If there is no declaration or stub for a subprogram, then the subprogram body
should also include all the documentation required for a subprogram
declaration (see paragraph 5.6.6.3).
c. Subprogram stubs should have the following format:
<subprogram specification> is separate;
where the <subprogram specification> is formatted as in a subprogram
declaration (see paragraph 5.6.6.3). If there is no previous declaration for
a separate subprogram, then the subprogram stub should be followed by the same
documentary comments required for a subprogram declaration (see paragraph
5.6.6.3).
5.6.6.5 Named parameter association.
a. Named parameter association should generally be used for procedure
calls of more than a single parameter. Positional parameters are generally
preferred for function calls.
b. Named and positional parameter associations should generally not be
mixed in a single subprogram call.
c. Named parameter associations should generally appear one to a line
with formal parameters, "=>" symbols and actual parameters aligned.
Obtain_Next_Token
( File => Current Source File,
Position => Current Column',
Token => Next_Token );
5.6.7 Examples for subprograms. See also examples 5.7.6.3, 5.9.14.3,
5.12.7.1, 5.12.7.3, 5.14.5.1, and 5.14.5.2.
5.6.7.1 Example 6X1.
-- .............................
-- . .
-- . Obtain_Next_Token . SPEC
-- . .
-- .............................
procedure Obtain_Next_Token
( File : in out Parser_Types.FILE; -- sequential file
Token : out Parser_Types_TOKEN_TYP;
Position : in Parser_Types.COL_NUM_TYP := 0 );
-- Purpose
-- This procedure scans the current input line from the point at
-- which it was last called and returns the next token.
--
-- Exceptions
--
-- Source_File_Not_Open -- Raised if the input file is not open
-- Notes (None)
5.6.7.2 Example 6X2.
-- ..........................
-- . .
-- . Decode_Token . SPEC
-- . .
-- ..........................
function Decode_Token
( File : Parser_Types.FILE;
Token : Parser_Types.TOKEN_TYP )
return Parser_Types.TOKEN_TYP;
-- Purpose
-- This function returns the ordinal value of the decoded token.
--
-- Exceptions
-- Illegal Token -- raised if the token is not legal
--
-- Notes
-- This function will later be changed to a procedure.
5.6.7.3 Example 6X3.
-- ...............................
-- . .
-- . Obtain_Next_Token . STUB
-- . .
-- ...............................
procedure Obtain_Next_Token
( File : in out Parser_Types.FILE;
Token : out Parser_Types.TOKEN_TYP;
Position : in Parser_Types. COL_NUM_TYP :=0 ) is separate;
5.6.7.4 Example 6X4.
-- .........................
-- . .
-- . Decode_Token . STUB
-- . .
-- .........................
function Decode_Token
( File : Parser_Types.FILE;
Token : Parser_Types.TOKEN_TYP )
return Parser_Types.TOKEN_TYP is separate;
5.6.7.5 Example 6X5.
-- ................................
-- . .
-- . Obtain_Next_Token . SUBUNIT
-- . .
-- ................................
with Parser_Types,
File_Handler;
separate (Lexical_Analyzer)
procedure Obtain Next Token
( File : in out Parser_Types.FILE;
Token : out Parser_Types.TOKEN_TYP;
Position : in Parser_Types.COL_NUM_TYP :=0 ) is
-- Notes (None)
--
-- Modifications
-- 7/4/85 Rebecca DeMornay Initial version of the subunit
-- 9/6/85 R. DeMornay Added the local function
-- "Increment_Line_Counter".
type LINE_COUNT is -- A count of the number
range 1 .. File_Handler_Max Size; -- of lines in a file.
Line_Counter : LINE_COUNT := 1;
-- ...............................
-- . .
-- . Increment_Line_Counter . SPEC & BODY
-- . .
-- ...............................
function Increment_Line_Counter
( File : Parser_Types.FILE;
Line : LINE_COUNT ) -- Line number in "File"
-- at the time of call
return LINE_COUNT is
-- Purpose
-- This function increments the line counter from the point at
-- which it was after the last call of this routine.
--
-- Exceptions
-- Source_File_Not_Open -- Raised if "File" is not open.
-- End_Of_File -- Raised if the function is called and
-- the end of the file has already been
-- reached.
--
-- Notes (None)
begin -- Increment_Line_Counter
...
end Increment_Line_Counter;
begin -- Obtain_Next_Token
exception
when File_Handler.FILE_ERROR => Token := Parse_Types.NONE;
raise Source_File_Not_Open;
end Obtain_Next_Token;
5.7 Packages.
5.7.1 Use of packages.
a. There are numerous roles for packages, the following represent the
most common:
(1) Model an abstract entity (or data type) appropriate to the domain
of a problem.
(2) Collect related type and object declarations which are used
together (this kind of package should generally be used only to provide a
common set of declarations for two or more library units).
(3) To group together program units for essential configuration
control (packages fulfilling this role alone should be used sparingly).
The roles above are listed in order of decreasing desirability. The first
role, modeling a problem domain entity, is the strongest use of packages for
structuring a program. It corresponds to the requirement of functional
cohesion for subprograms (see paragraph 5.6.1) and contributes to the goal of
making the structure of a program reflect the structure of its problem domain.
The second kind of package, collection of related declarations, should
generally be used only to provide a common set of declarations for two or more
library units. Further, it is better to minimize the declaration of variables
in these packages. Overuse of packages of variables results in a FORTRAN
COMMON block style program decomposition which defeats the abstraction and
information hiding properties of packages (see paragraph 5.7.4).
Finally the last type of package, a grouping of units for configuration
reasons, should be used sparingly since it gives no additional information to
a human reader on the structure of the program. This type of package might,
for example, be used to divide a large program at the top level into
subsystems to be developed by separate teams. It would be best, however, if
these subsystem packages fulfilled, in addition, one of the other two roles.
b. Packages should NOT be designed based on the procedural structure of
the code which calls them.
For example, a group of procedures should not be packaged simply because they
are all called at system initialization, or because they are always called in
a certain sequence. Such a package is closely coupled to the context in which
it is used and is not very understandable, reusable or maintainable as a unit.
c. A logical hierarchy of packages should be used to reflect or model
levels of abstraction.
5.7.2 Nesting. The use of nesting is not recommended because of the
numerous precautions and limitations involved.
5.7.3 Initialization. The initialization process should be self-contained.
It is poor programming practice to call from the initialization statement of
a package to subprograms outside the package and should be avoided.
5.7.4 Visible variables.
a. Variable declarations in package specifications should be minimized.
One of the aspects of software engineering Ada supports so well is information
hiding. The use of variables in a package specification generally reduces the
abstraction and information hiding properties of the package. For example, a
variable cannot provide protection against being changed by units other than
the package. Therefore it is generally better to use a function rather than a
variable to read data from a package. It is also generally better to use a
procedure rather than a variable to give data to a package, since a variable
cannot trigger any package operations and a variable declaration often exposes
some internal data representation details of the package.
b. The private part of a package specification should only be used to
supply the full definitions of private types and deferred constants; all other
declarations should be put in the package body.
c. Object of private type should be initialized by default, if possible.
5.7.5 Formatting of packages.
5.7.5.1 Package names. A package name should be a noun phrase describing the
abstract entity modeled by the package, or simply whatever is being packaged.
Stack_Handler
Vehicle_Controller
Terminal_Operations
Parser_Types
Utilities_Package
5.7.5.2 Package header. Each package specification, body or stub should be
preceded by a header comment block containing at least the package name and
the indication SPEC, BODY, STUB or SUBUNIT. A suggested format follows:
-- ***************************
-- * *
-- * Lexical_Analyzer * SPEC
-- * *
-- ***************************
5.7.5.3 Package specifications.
a. Package specifications should have the-following format: package
<package identifier> is
-- <documentary comments>
<declaration>
<declaration>
private -- <package identifier>
<declaration>
<declaration>
end <package identifier>;
The <documentary comments> should follow guideline b., below. Note that the
<package identifier> should always be repeated at the "end" of the package
specification.
b. A package specification should include AT LEAST the following
documentation immediately after the package header:
-- Purpose
-- A description of the purpose and function of the package.
--
-- Initialization Exceptions
-- A list of all exceptions which may propagate out of the
-- package INITIALIZATION PART and a description of when each
-- would be raised.
--
-- Notes
-- Additional comments on the use of the package.
The "Initialization Exceptions" and "Notes" headers should be included even if
these sections are empty. An empty section may be indicated by placing the
annotation "(none)" after the appropriate header. Only in the case a package
specification which is a compilation unit, the following section should be
added to the documentation:
-- Modifications
-- A list of modifications made to the package SPECIFICATION.
-- Each entry in the list should include the date of the change,
-- the name of the person who made the change and a description
-- of the modification. The description should indicate exactly
-- where in the compilation unit that the change was made. The
-- first entry in the list should always be the initial coding of
-- the package specification.
c. In a declarative part, all package specifications should appear before
any package or task bodies.
5.7.5.4 Package bodies and stubs.
a. Package bodies should have the following format:
separate (<parent name>)
package body <package identifier> is
-- <documentary comments>
<declaration>
<declaration>
begin -- <package identifier>
<statement>
<statement>
exception
when <exception> =>
<statement>
end <package identifier>;
The <documentary comments> should follow guideline b below. Note that the
<package identifier> should always be repeated at the "end" of the package
body.
b. A package body should have at least the following documentation placed
immediately after the package header:
-- Notes
-- Comments on the design, implementation and use of the
-- package.
The "Notes" header should be included even if the section is empty. An empty
section may be indicated by the comment "Notes (none)." Only in the case of a
package body which is a compilation unit, should the following section be
added to the documentation:
-- Modifications
-- A list of modifications made to the package BODY. Each
-- entry in the list should include the date of the change,
-- the name of the person who made the change and a
-- description of the modification. The description should
-- indicate exactly where in the compilation unit that the
-- change was made. The first entry in the list should always
-- be the initial coding of the package body.
c. Package stubs should have the following format: package body <package
identifier> is separate;
5.7.6 Examples for packages.
See also example 5.12.7.3
5.7.6.1 Example 7X1.
-- ***************************
-- * *
-- * Lexical Analyzer * SPEC
-- * *
-- ***************************
with Basic_Types,
Parser_Types;
package Lexical_Analyzer is
-- Purpose
-- The routines in this package read the source program, one
-- character at a time, to generate a stream of tokens. As each
-- token is produced it is passed to the package "Parser." The
-- legal tokens are defined in the Language Reference Manual.
--
-- Initialization Exceptions
--
-- Diana_ File_ Non_Existent -- Raised if the file "DIANA.ADA"
-- does not exist
-- Notes
-- Tokens are limited to 32 characters in length. Also, only
-- sequential text files can be operated on by the parser.
-- Modifications
--
-- 6/14/85 Rebecca DeMornay Initial version of spec
-- 8/26/87 C. Royale Added "Decode_Token" function.
Diana_File_Non_Existent
: exception;
Source_File_Not_Open
: exception;
Illegal_Token
: exception;
-- .................................
-- . .
-- . Obtain_Next_Token . SPEC
-- . .
-- .................................
procedure Obtain_Next_Token
(
...
);
...
-- ....................
-- . .
-- . Decode_Token . SPEC
-- . .
-- ....................
function Decode_Token
(
...
)
return Parser_Types.TOKEN_VALUE_TYP;
...
end Lexical_Analyzer;
5.7.6.2 Example 7X2.
-- ***************************
-- * *
-- * Lexical Analyzer * BODY
-- * *
-- ***************************
with Text_IO,
File_Handler;
package body Lexical_Analyzer is
-- Notes
-- The package "Lexical_Analyzer" will later be changed to:a task,
-- so that the "Parser" task (now a package) can make an entry
-- call to "Lexical_Analyzer" when it needs the next token.
--
-- Modifications
-- 6/14/85 Charity Royale Initial version of body.
-- 8/26/85 Charity Royale Added "Decode_Token" function.
-- Added instantiation of "Enumeration_IO."
-- ******************
-- * *
-- * Char_IO * SPEC
-- * *
-- ******************
package Char_IO is
new Text_IO.Enumeration IO (Enum => Character);
-- Purpose
-- Used to read the input text file character by character.
--
-- Initialization Exceptions (none)
-- Notes (none)
-- ..................................
-- . .
-- . Obtain_Next_Token . STUB
-- . .
-- ..................................
procedure Obtain_Next_Token
(
...
) is separate;
-- ......................
-- . .
-- . Decode_Token . STUB
-- . .
-- ......................
function Decode_Token
(
...
)
return Parser_Types.TOKEN_VALUE_TYP is separate;
...
begin -- Lexical_Analyzer
...
exception
when File_Handler.File_Error =>
raise Diana_File_Non_Existent
end Lexical_Analyzer;
5.7.6.3 Example 7X3.
-- *********************
-- * *
-- * Disk * SPEC
-- * *
-- *********************
generic
type SPECIFIC_DATA_TYP is -- The type of data to be
( <> ); -- stored on disk
package Disk is
-- Purpose
-- This package defines an abstract data type to simplify
-- the I/O interface to disk files.
--
-- Initialization Exceptions (none)
-- Notes (none)
-- Modifications
-- 9/10/86 Ada Users Group Initial version
type FILE_TYP is private;
End_Of_File
: exception;
Open_Error
: exception;
Mode_Error
: exception;
subtype FILE_MODE is
(IN_FILE, OUT_FILE);
-- .....................
-- . .
-- . Create . SPEC
-- . .
-- .....................
function Create
( Name : STRING;
Mode : FILE_MODE := IN_FILE )
return FILE_TYP;
-- Purpose
-- This function creates a FILE_TYP data object to
-- represent the disk file with the given name and mode.
--
-- Exceptions (none)
-- Notes
-- This function does not actually open the file.
-- .....................
-- . .
-- . Close . SPEC
-- . .
-- .....................
procedure Close
( Disk_File : in out FILE_TYP );
-- Purpose
-- This procedure closes a disk file if it is open. If
-- the file is already closed it has no effect.
--
-- Exceptions (none)
-- Notes (none)
-- ....................
-- . .
-- . Read . SPEC
-- . .
-- ....................
procedure Read
( Disk_File : in out FlLE_TYP;
Data : out SPECIFIC_DATA_TYP );
-- Purpose
-- This procedure reads the next record from a file,
-- opening the file if necessary.
--
-- Exceptions
-- End_Of_File - raised if no more elements can be
-- read from the file
-- Open_Error - if the file cannot be opened
-- Mode_Error - if the file mode is not IN FILE
--
-- Notes (none)
-- ......................
-- . .
-- . Write . SPEC
-- . .
-- ......................
procedure Write
( Disk_File : in out FILE_TYP;
Data : in SPECIFIC_DATA_TYP );
-- Purpose
-- This function writes a record to a file,
-- opening the file if necessary.
--
-- Exceptions
-- Open_Error - if the file cannot be opened
-- Mode_Error - if the file mode is not OUT_FILE
--
-- Notes (none)
private -- Disk
-- *********************
-- * *
-- * Disk_IO * SPEC
-- * *
-- ********************* package Disk_IO is
new Sequential_IO (SPECIFIC_DATA_TYP);
-- Purpose -- This package provides the basis for the representation
-- of disk files.
--
-- Initialization Exceptions (none)
-- Notes (none)
File_Name_Length
: constant := 40;
type FILE_TYP is
record
File_Name : STRING (1..File_Name_Length) := (others => ' ');
File : Disk_IO.FILE_TYP;
Mode : FILE_MODE := Disk_IO.IN_FILE;
end record;
end Disk;
5.8 Visibility.
5.8.1 Scope of identifiers. The scope of identifiers should not extend
further than necessary. Where a scope is extended by "with" clauses, these
clauses should cover as small a region of text as possible.
For example, "with" clauses should be placed only on the subunits that really
need them, not on their parents. This promotes information hiding and reduces
coupling. It can also result in faster recompilation (due to the dependency
rules).
5.8.2 The package STANDARD and WITH clauses. The package STANDARD should
not be named in a "with" clause.
5.8.3 The use clause. The "use" clause should be employed only in a limited
application. It detracts from the readability of the program by allowing
the "expanded" name to be shorten (not having to specify the "prefix", not
having to use the dot notation). Thus, the understandability of the program
may be affected. Another detraction, there is an outside possibility it may
introduce a name clash.
5.8.4 Renaming declarations. Usually renaming causes more problems than it
is worth; so again, use it in a limited fashion, if at all.
a. For a name with a large number of package qualifications, a renaming
declaration may be used to define a new shorter name. The new identifier
should still reflect the complete meaning of the full name.
b. For a function which can be appropriately represented by an operator
symbol name, a renaming declaration may be used to give it such a name.
However, notice that a renaming declaration may have some appropriateness
for achieving direct visibility, e.g.
A Matrix_ Multiply function could be renamed "*".
5.8.5 Redefinition.
a. It is extremely poor programming practice to redefine or rename items
from the package STANDARD.
b. Redefinition of an identifier in different declarations should be
avoided.
5.9 Tasks.
5.9.1 Use of tasks. A task should fulfill one or more of the following:
a. Model a concurrent abstract entity appropriate to the problem domain.
b. Serve as an aceess-controlling or synchronizing agent for other tasks,
or otherwise act as an interface between asynchronous tasks.
c. Serve as an interface to asynchronous entities external to the program
(e.g., asynchronous I/O, devices, interrupts, etc.).
d. Define concurrent algorithms for faster execution on multiprocessor
architectures.
e. Perform an activity which must wait a specified time for an event or
have a specific priority.
Just as for packages (paragraph 5.7.1) it is best to have tasks which model
problem domain entities. However, in the case of tasks it is also necessary
to have some tasks which provide interfaces between other tasks and which
handle the other issues of concurrency and parallelism mentioned above. The
program should generally be structured, however, around the tasks which
represent problem-domain entities.
5.9.2 Nesting of tasks.
a. Tasks should generally not be nested within tasks or subprograms,
except for the main procedure.
Note that a subprogram containing a task cannot return until the task has
terminated.
b. Nested task bodies should be separate subunits, unless they are quite
small.
5.9.3 Visibility of tasks.
a. When only certain entries of a task are intended to be called by
program components outside an enclosing package, it is generally preferable to
hide the task specification in the package body, introducing package
procedures which in turn call the actual entries.
b. This helps to promote information hiding and strengthens the
abstraction of the enclosing package (see paragraph 5.7.2.d). It also hides
the use of tasking within the package. Note, however, that special care must
be taken if the task entries are to be called using conditional or timed entry
calls. In this case either the outer package must provide special procedures
or procedure parameters or this guideline should not be followed.
5.9.4 Task types.
a. A task type should be used only when multiple instances of that type
are required. Otherwise a directly named task should be used.
b. Identical tasks should be derived from a common task type.
c. Static task structures should be used whenever they are sufficient.
Access types to task type should be used only when it is essential to create
and destroy tasks dynamically, or to be able to change the names with which
they are associated.
5.9.5 Task termination. Nesting of tasks within other tasks should be
avoided. If tasks must be nested, they should be forced to terminate when
they are suppose to by causing them to reach their end statement. If a task
is a "active" task, this is done by having the main loop as a "while" loop.
If the task is "passive", there should be an entry which causes the main loop
to be exited.
5.9.6 Entries and accept statements.
a. Only those actions should be included in the "accept" statement which
must be completed before the calling task is released from its waiting state.
b. Conditional entry calls should be used sparingly to avoid unnecessary
busy waiting.
5.9.7 Delay statement.
a. A "delay" statement should be used whenever a task must wait for some
known duration. A "busy wait" loop should never be used for this purpose.
It is important to remember that "delay t" provides a delay of at least t
seconds, but possibly more. A program should not rely on any upper bound for
this delay, especially when tasks are used (since tasks must compete for CPU
time).
The following example is only one of many techniques to alleviate this
problem in a periodic activity:
...
Next_Time := Calendar.Clock + Required_ Period;
Periodic_Activity:
while Still_Time.loop
-- Perform activity
...
-- Correct for delay statement incertitude
Period := Next_Time - Calendar.Clock;
if Period < 0.0 then -- Processing was too slow
Next_Time := Calendar.Clock -- Avoid cumulative effect
end if;
Next_Time := Next_Time + Required_Period;
delay Period;
end loop Periodic_Activity;
b. The "delay" statement should normally only be used to manage
interaction with some external process which works in real time, or to create
a task which behaves in a well-defined manner in real time.
5.9.8 Task synchronization. Knowledge of the execution pattern of tasks
(e.g., fixed, known time pattern, etc.) should not be used to avoid the use of
explicit task synchronization.
5.9.9 Priorities.
a. Only a small number of priority levels should be used. The priority
levels used should be spread over the range made available to type PRIORITY in
the implementation. Names should be given to the priority levels by declaring
constants of predefined type PRIORITY and grouping these declarations into a
single package.
Using only a small number of priority levels makes the interaction of the
various prioritized tasks easier to understand. On the other hand, spreading
the levels across the available range allows easy insertion of a new level
between existing levels if this later becomes necessary. As with other
literal numbers, the use of names is more readable than the use of the
literals.
Further, for priorities, the allowable range of levels is implementation
dependent. Naming priority levels by constant declarations grouped into a
single package restricts the implementation dependency to that package. For
example:
with System;
package Priority_Levels is
Lowest_Priority
: constant System.PRIORITY := System.PRIORITY'first;
Highest_Priority
: constant System.PRIORITY := System.PRIORITY'last;
Median
: constant := Highest_Priority - Lowest_Priority + 1;
Average
: constant System.PRIORITY := Lowest_Priority + (Median / 2);
Idle
: constant System.PRIORITY := Lowest_Priority;
Background
: constant System.PRIORITY := Lowest_Priority + (Median / 5);
User
: constant System.PRIORITY := Lowest_Priority + (2 * Median / 5);
Foreground
: constant System.PRIORITY := Lowest_Priority + (4 * Median / 5);
end Priority_Levels;
b. For any group of related tasks, such as those declared within the same
program unit, priorities should be specified either for all, or for none of
them.
This avoids confusion about the scheduling of tasks with undefined priorities.
5.9.10 Abort statements. Abortion of tasks should generally be avoided.
Aborting a task can produce unpredictable results. In particular, do not
assume anything about the moment at which an aborted task becomes terminated.
The "abort" statement should generally be used only in case of unrecoverable
failure.
5.9.11 Shared variables.
a. Tasks should not directly share variables unless only one of them can
possibly be running at any one time.
b. Any task which uses shared variables should identify in its
documentary comments all the shared variables that it uses.
5.9.12 Local exception handling. To allow the handling of local exceptions
without task termination, a task should generally have a block statement with
an exception handler coded within its main loop.
begin -- Some Task
Main_Loop:
loop
Local:
begin
-- Task code
...
exception -- Local
-- handle local exceptions ...
end Local;
end Main_Loop;
exception
-- handle fatal exceptions ...
end Some_Task;
5.9.13 Formatting of tasks.
5.9.13.1 Task and entry names.
a. A task name should be a noun phrase describing the task function or
abstract entity modeled by the task.
Sensor_Interface
Status_Monitor
Event_Handler
Message_Buffer
b. Entry names should follow the same guidelines as for subprogram names
(see paragraph 5.6.6.1).
5.9.13.2 Task and entry headers. Each task or task type specification or
body and each entry specification should be preceded by a header comment block
containing at least the unit name and the indication SPEC, BODY or STUB.
-- **********************
-- * *
-- * Buffer * SPEC
-- * *
-- **********************
task Buffer is
-- ....................
-- . .
-- . Read . SPEC
-- . .
-- ....................
entry Read ( Output : out Character );
5.9.13.3 Task specifications.
a. Task specifications should have the following format:
task <task identifier> is
-- <documentary comments>
<declaration>
<declaration>
end <task identifier>;
Guidelines for <documentary comments> should be AT LEAST as rigorous as those
for a subprogram declaration (see paragraph 5.6.6.3), except for the
"Exceptions" section.
NOTE: The <task identifier> should always be repeated at the "end" of the
task specification.
b. A task type specification should be formatted the same as a task
specification, with the exception of including "task type" in the header.
c. Entry declarations should have the following format:
entry <entry identifier> (<family range>)
( <parameter specification>;
<parameter specification> );
-- <documentary comments>
Each <parameter specification> should be formatted like an object declaration
(see paragraph 5.3.8.4). Guidelines for <documentary comments> should be AT
LEAST as rigorous as those for a subprogram declaration (see
paragraph 5.6.6.3).
d. Parameter mode indications should always be used in entry
declarations.
e. In a declarative part, all task specifications should appear before
any task or package bodies.
5.9.13.4 Task bodies and stubs.
a. Task bodies should have the following format:
separate (<parent>)
task body <task identifier> is
-- <documentary comments>
<declaration>
<declaration>
begin -- <task identifier>
<statement>
<statement>
exception
when <exceptions> =>
<statement>
end <task identifier>;
b. Guidelines for <documentary comments> should be AT LEAST as rigorous
as those for a subprogram declaration (see paragraph 5.6.6.3).
NOTE: The <task identifier> should always be repeated at the "end" of the
task body.
c. Task stubs should have the following format:
task body <task identifier> is separate;
5.9.13.5 Accept statements.
a. "Accept" statements should have one of the following formats:
accept <entry identifier> (<entry index>);
accept <entry identifier> (<entry index>)
( <parameter specification>;
<parameter specification> )
do
<statement>
<statement>
end <entry identifier>;
Each <parameter specification> should be formatted like an object declaration
(see paragraph 5.3.8.4). Note that the <entry identifier> should always be
repeated at the "end" of the "accept" (if there is an "end").
b. Parameter mode indications should always be used in accept statements.
5.9.13.6 Select statements.
a. Selective wait statements should have the following format:
select
<statement>
<statement>
or
<statement>
<statement>
or
when <condition> =>
<statement>
<statement>
end select;
This format is consistent with the indentation style of other statements. In
addition, the added level of indentation especially highlights guarded
sections of code.
b. Conditional and timed entry calls should have the following format:
select
<entry call>
<statement>
else
<statement>
<statement>
end select;
5.9.13.7 Pragma priority. The priority pragma should appear in task
specification before any entry declarations, and in the main program before
any declarations.
5.9.14 Examples for tasks.
5.9.14.1 Example 9X1.
-- **********************
-- * *
-- * Buffer * SPEC
-- * *
-- **********************
task Buffer is
-- Purpose
-- This task provides a character buffer to smooth variations
-- between the speed of output of a producing task and the speed
-- of input of a consuming task.
--
-- Exceptions (none)
-- Notes (none)
-- .....................
-- . .
-- . Read . SPEC
-- . .
-- .....................
entry Read ( Output : out Character );
-- Purpose
-- This entry reads a character from the buffer.
-- If the buffer is empty, the entry will wait
-- until a character is written into the buffer.
--
-- Exceptions (none)
-- Notes (none)
-- .....................
-- . .
-- . Write . SPEC
-- . .
-- .....................
entry Write ( Input : in Character );
-- Purpose
-- This entry writes a character into the buffer.
-- If the buffer is full the entry will wait
-- until a character is read from the buffer.
--
-- Exceptions (none)
-- Notes (none)
end Buffer;
5.9.14.2 Example 9X2.
-- **********************
-- * *
-- * Buffer * BODY
-- * *
-- **********************
separate (Buffer Package)
task body Buffer is
-- Notes
-- This task contains an internal pool of characters processed
-- in a round-robin fashion.
--
-- Modifications
-- 7/2/86 Fred Blah Initial version.
Pool_Size
: constant := 100;
subtype POOL_RANGE is
INTEGER range 1..Pool_Size;
type POOL_TYP is
array (POOL_RANGE) of CHARACTER;
Pool
: POOL_TYP;
Count -- The number of characters in
: INTEGER range 0..Pool_Size := 0; -- the pool.
In_Index -- The space for the next input
: POOL_RANGE := 1; -- character.
Out_Index -- The space for the next output
: POOL_RANGE := 1; -- character.
begin -- Buffer
Pool_Loop:
loop
select
when Count < Pool_Size =>
accept Write ( Input : in Character ) do
Pool(In_Index) := Input;
end write;
In_Index := In_Index mod Pool_Size + 1;
Count := Count + 1;
or
when Count > 0 =>
accept Read ( Output : out Character) do
Output := Pool(Out_Index);
end Read;
Out_Index := Out_Index mod Pool_Size + 1;
Count := Count - 1;
or
terminate;
end select;
end loop Pool_Loop;
end Buffer;
5.9.14.3 Example 9X3.
-- ...................
-- . .
-- . Shellsort . BODY
-- . .
-- ...................
procedure Shellsort
( List : in out ITEM_LIST; -- to be sorted in place.
Number_Of_Items : in NATURAL );
-- Purpose
-- This is a basic integer sorting routine.
-- Notes
-- This sorting procedure implements the Shell sort by
-- separating the n-sorts into multiple Ada tasks.
-- This algorithm is designed for parallel processing
-- of the tasks and is not an efficient method on a
-- single processor. The process works on an one
-- dimensional array called ITEM_LIST.
--
-- Modifications
-- 9/5/86 A. Shell Initial version
Increment -- Increment of an n-sort
: NATURAL;
Number_of_Sorts -- Number of parallel sorts
: NATURAL; -- for a single pass
Number_Of_Tasks
: NATURAL;
-- ********************
-- * *
-- * SORTER TASK * SPEC
-- * *
-- ********************
task type SORTER_TASK is
-- Purpose
-- Tasks of this type perform the n-sort for the Shell sort.
--
-- Notes
-- A SORTER_TASK terminates itself when it is no longer
-- needed for the sort.
-- ......................
-- . .
-- . Sort . SPEC
-- . .
-- ......................
entry Sort
( First : in INTEGER;
Step : in NATURAL );
-- Purpose
-- This entry signals a sorter task to perform a new
-- n-sort. Elements are sorted in place in List,
-- starting with the element at index First and
-- including subsequent elements at the indicated Step.
--
-- Exceptions (none)
-- Notes (none)
end SORTER_TASK;
-- *********************
-- * *
-- * SORTER_TASK * STUB
-- * *
-- *********************
task body SORTER_TASK is separate;
type SORTER_ARRAY is
array (INTEGER range <>) of SORTER_TASK;
begin -- Shellsort
if Number_Of_Items < 2 then
return;
end if;
-- Determine the first n-sort increment.
Increment := 1;
while Increment < Number_Of_Items loop
Increment := (3 * Increment) + 1;
end loop;
Increment := Increment / 3;
if Increment < 1 then
Increment := 1;
end if;
-- Determine the number of tasks required to perform
-- the sort.
if Number_Of_Items / Increment = 1 then
Number_of_Sorts := Number Of Items mod Increment;
if Increment / 3 > Number Of Sorts then
Number_Of_Tasks := Increment / 3;
else
Number_Of_Tasks := Number_Of_Sorts;
end if;
else
Number_Of_Sorts := Increment;
Number_of_Tasks := Number_Of_Sorts;
end if;
-- Perform the sort
Task_Block:
declare
Sort_List
: SORTER_ARRAY (1 .. Number_Of_Tasks);
begin -- Task_Block
Incrementation:
while Increment > 0 loop
Sort_Work:
for K in 1 .. Number_Of_Sorts loop
Sort_List(K).Sort
( First => List'first + K - 1'
Step => Increment );
end loop Sort_Work;
Increment := Increment / 3;
Number_of_Sorts := Increment;
end loop Incrementation;
end Task_Block;
end Shellsort;
5.9.14.4 Example 9X4.
-- *********************
-- * *
-- * SORTER_TASK * SUBUNIT
-- * *
-- *********************
separate (Shellsort)
task body SORTER_TASK is
-- Notes
-- This task body implements a task type.
--
-- Modifications
-- 9/5/86 A. Shell Initial version
-- Global variables
-- List -- An array of all items to be
sorted by Shellsort.
-- Number_Of_Items -- The number of items in the list.
Start : INTEGER;
Increment : NATURAL;
A : INTEGER;
B : INTEGER;
First_B : INTEGER;
Temp : INTEGER;
begin -- SORTER_TASK
Main_Loop:
loop
accept Sort
( First : in INTEGER;
Step : in NATURAL )
do
Start := First;
Increment := Step;
end Sort;
First_B := Start + Increment;
Determine_Criteria:
while First_B <= List'first + Number_Of_Items - 1 loop
B := First_B;
A := B - Increment;
Find_Position:
while A >= Start loop exit when not (List(A) > List(B));
Temp := List(A);
List(A) := List(B);
List(B) := Temp;
B := A;
A := B - Increment;
end loop Find_Position;
First_B := First_B + Increment;
end loop Determine_Criteria;
-- Terminate if task is not needed for n-sort
exit when (Increment / 3) < Start - List'first + 1;
end loop Main_Loop;
end SORTER_TASK;
5.10 Program structure and compilation issues.
5.10.1 Library units. Library units are divided into two categories,
subprograms and packages. Library subprograms define one entity, whereas
packages provide services which define more than on entity. Some of the
primary uses are:
a. To allow configuration control of the high level functional
subsystems of a program.
b. For general purposes, reusable program units.
5.10.2 WITH clauses.
a. No unit should have a "with" clause for a unit it does not need to see
directly.
b. If only a small part of a given unit needs access to a library unit,
then it should generally appear as a subunit and have its own "with" clause
for that library unit (see paragraph 5.8.1).
NOTE: Understanding the use of the "with" clause is important. Where the
"with" is interpreted, based on the compiler, can cause recompilation
problems by hiding names.
5.10.3 Program unit dependencies.
a. Excessive dependencies between compilation units should be avoided,
especially the use of complicated networks of "with" clauses.
b. It is preferable to limit program unit dependencies to a logical tree
structure whenever possible. In this instance, the preferred tree stucture
would be where a parent can have multiple children; however, every child has
only one parent. Any program that creates a dependency of multiple parents
to multiple children should be evaluated for redesign.
5.11 Exceptions.
5.11.1 Exception propagation.
a. Exceptions propagated by a program unit should be considered part of
the abstraction or function represented by that unit. Therefore, it should
generally only propagate exceptions which are appropriate to that level of
abstraction. If necessary, an exception which cannot be handled by a unit at
one level of abstraction should be converted into an exception which can be
explicitly recognized by the next higher level.
For example, a Stack package should provide a Stack_Full exception instead of
propagating a Constraint_Error. Similarly, a Matrix_lnverse function should
raise a Matrix_is_Singular exception rather than propagating Numeric_Error.
b. Exceptions should not be allowed to propagate outside their own scope.
An exception may be allowed to propagate to any point where it can be named in
an exception handler. Note that this includes the case where an exception is
defined in a package specification and has its scope "expanded" by a "with"
clause. What must be avoided are cases such as the following:
procedure Raise_Exception is
Hidden_Exception : exception;
begin
raise Hidden_Exception;
end Raise_Exception;
begin
Raise_Exception;
-- "Hidden_Exception" CANNOT be named at this point
5.11.2 Use of exceptions.
a. An exception should be used sparingly. If used, extensive and clear
comments should be included. Possible reasons for exceptions are:
(1) It reports an irregular event which is outside the normal
operation of a program unit or is in some sense an error.
(2) It is used where it can be argued that it is safer (more
defensive) than the alternative, in particular to guard against omissions of
error checking code for especially harmful errors.
(3) It reports an event for which it is inconvenient or unnatural to
test at the point of cause/occurrence and thus use of the exception enhances
readability. Exceptions declared in package specifications are really part of
the abstraction defined by that package. Therefore their use should be
integral to the design of the package (see paragraph 5.10.1).
Also, note that the predefined exceptions should be used with care. Due to
allowable implementation differences, they should not be relied upon to
indicate particular circumstances.
b. Exceptions should not be used as means of returning normal state
information.
For example, a Stack package may have Stack Full and Stack Empty exceptions
which are raised by its Push and Pop subprograms. However, these subprograms
should not be used solely to raise exceptions to test if the appropriate
conditions are true. Instead, the package should provide BOOLEAN functions
such as Full and Empty to test for these state conditions.
5.11.3 Exception handlers.
a. The exception handler choice "others" should be used only if it is
necessary to ensure that no UNANTICIPATED exception can be propagated or if
some special action must be taken before propagation.
For example, important tasks should generally have an "others" clause in a
local exception handler (see paragraph 5.9.12) to prevent them from
terminating due to unanticipated exceptions. However, in the case when it can
be expected that a certain exception may sometimes occur, then that exception
should always be explicitly named in the exception handler.
b. Recursion should not be used within an exception handler.
c. Exception handlers on block statements should be used sparingly. One
of the advantages of using exceptions is that it separates the error handling
code from the more often executed normal processing code. Excessive use of
exception handlers in block statements can defeat this advantage.
5.11.4 Raise statements.
a. Exceptions declared in the specification of a package which represents
a problem domain entity should not be raised outside that package.
Exceptions declared in a package specification should be considered part of
the abstraction defined by the package. These exceptions provide special
"signals" from the package operations, and should thus not be raised outside
of the package.
b. Exceptions raised within a.task should always-be handled within the
task.
NOTE: In the case of an exception raised during a rendezvous the exception
will also be propagated back to the point of the entry call.
c. The predefined exceptions should generally not be explicitly raised.
5.11.5 Suppressing checks. Checks should not be suppressed except for
essential efficiency or timing reasons in thoroughly tested program units.
5.11.6 Exception declarations. Exception declarations should re formatted
like object declarations, paragraph 5.3.8.4.
5.11.7 Examples for exceptions. See examples 5.5.9.4, 5.6.7.5, 5.7.6.2,
and 5.14.5.2.
5.12 Generic units.
5.12.1 Use of generic units.
a. Generics should not be used in situations in which normal programming
constructs are equivalent.
b. A generic program unit should fulfill one or more of the following:
(1) Provide logically equivalent operations on objects of different
type.
(2) Parameterize a program unit by a subprogram value.
(3) Provide a data abstraction required at many points in a program,
even if no parameterization is required.
(4) Provide parameters which are particularly appropriate to be fixed
at declaration or elaboration time.
(5) Reduce coupling by controlling visibility.
c. Functions should be made generic whenever possible to facilitate and
increase reusability.
5.12.2 Generic library units. Generic units should generally be library
units.
5.12.3 Generic instantiation.
a. The most commonly used generic instantiations should generally be
placed in library units.
b. Generic instantiations should be used cautiously within generic units.
NOTE: Information presented in some cases fall into the realm of guidelines
to compensate for compiler and/or run-time deficiencies. Sub-subparagraph
b is an example.
5.12.4 Generic formal subprograms.
a. The actual subprograms associated with the formal subprogram
parameters of a generic unit should be consistent with the conceptual meanings
of the formal parameters (e.g., only functions which are conceptually "adding
operations" should be associated with a formal parameter named "plus").
b. Operator symbol function generic parameters should generally be
provided with a box default body ("is <>").
with function "<"
( X : ITEM;
Y : ITEM )
return BOOLEAN is <>;
5.12.5 Use of attributes.
In writing generic bodies, attributes should be used as much as possible
to generalize the code produced.
5.12.6 Formatting of generic units.
5.12.6.1 Generic declarations.
a. Generic declarations should have the following format:
generic
<declaration>
<declaration>
<program unit specification>;
-- <documentary comments>.
Each <declaration> should be formatted like its non-formal counterpart (see
paragraphs 5.3.8.3 and 5.3.8.4), except for formal subprograms which should be
formatted as in b below. The <program unit specification> should be formatted
as for non-generic units (see paragraphs 5.6.6.3 and 5.7.5.3).
b. A generic formal parameter subprogram declaration should have one of
the following formats:
with <subprogram specification>;
-- <purpose>
with <subprogram specification> is <>;
-- <purpose>
with <subprogram specification> is <default name>;
-- <purpose>
The <subprogram specification> should be formatted as for a subprogram
declaration (see paragraph 5.6.3.3). Note, however, that the only
documentation generally needed on formal subprograms is the "Purpose."
c. A generic declaration should be preceded by the appropriate unit
header block (see paragraphs 5.6.6.2 and 5.7.5.2).
5.12.6.2 Formatting of generic instantiations.
a. Generic instantiations should have one of the following formats:
<unit header> is
new <generic name> (<generic argument>, <generic argument>);
-- <documentary comments>
<unit header> is
new <generic name>
( <generic parameter> => <generic argument>,
<generic parameter> => <generic argument>);
<documentary comments>
NOTE: In the second form the arrows ("=>") should be kept aligned. Also, the
<documentary comments> should not duplicate information presented in the
generic specification. A referral back to the original comments should be a
good beginning.
b. Generic instantiations should have the same kind of header comment
block as for a specification of the appropriate kind of unit (paragraphs
5.6.6.2 and 5.7.5.2).
5.12.7 Examples for generic units. See also examples 5.7.6.2 and 5.7.6.3.
5.12.7.1 Example 12X1.
-- ........................
-- . .
-- . Shellsort_Generic . SPEC
-- . .
-- ........................
generic
type ITEM is private; -- The type of items sorted
type LIST_TYP is -- The type of the item list
array (INTEGER range <>) of ITEM;
with function "<"
( Left : ITEM;
Right : ITEM )
return BOOLEAN is <>;
-- Purpose
-- This function defines the ordering used when the
-- items are sorted.
procedure Shellsort_Generic
( List : in out LIST_TYP ; -- This list will be sorted in place.
N : in NATURAL ); -- The number of items in the list.
-- Purpose
-- This procedure sorts the items in List using a Shell
-- sort algorithm designed for parallel processing.
--
-- Exceptions (none)
--
-- Notes
-- (This is a generic declaration for the procedure
-- body five in example 9X3.)
--
-- Modifications
-- 9/5/86 A. Shell Initial version
5.12.7.2 Example 12X2.
-- .....................
-- . .
-- . Name_Sort . SPEC
-- . .
-- .....................
procedure Name_Sort is
new Shellsort_Generic
( ITEM => NAME,
LIST_TYP => NAME_LIST );
5.12.7.3 Example 12X3.
-- *************************
-- * *
-- * *
-- * Unit_Statistics * SPEC
-- * *
-- *************************
with Text_IO;
generic
Unit_Name -- Name of the unit for which
: STRING; -- statistics are to be kept.
type ELEMENT_TYP is -- Enumeration type of the elements
(<>); -- to be counted.
package Unit_Statistics is
-- Purpose
-- This package provides operations to keep counts for the
-- various elements of a program unit. These counts can be
-- incremented or printed out in a report.
--
-- Initialization Exceptions (none)
--
-- Notes
--
-- This package is based on the generic package "Task_Statistics"
-- written by Dan Roy.
--
-- Modifications
-- 8/18/86 Ed Seidewitz Initial version
-- ............................
-- . .
-- . Number_Of_Lines . SPEC
-- . .
-- ............................
function Number_Of_Lines
return POSITIVE;
-- Purpose
-- This function returns the number of lines printed by
-- procedure Report since the last call to Number_Of_Lines.
--
-- Exceptions (none)
--
-- Notes (none)
-- ...........................
-- . .
-- . Count_of . SPEC
-- . .
-- ...........................
function Count_Of
( Element : ELEMENT_TYP ) return NATURAL;
-- Purpose
-- This function returns the current count for the specified
-- element.
--
-- Exceptions (none)
--
-- Notes (none)
-- ...................
-- . .
-- . Increment . SPEC
-- . .
-- ...................
procedure Increment
( Element : in ELEMENT_TYP;
By_Amount : in INTEGER := 1 );
-- Purpose
-- This procedure increments the count for the specified
-- element by a certain amount. By default, this amount
-- is one.
--
-- Exceptions (none)
--
-- Notes (none)
-- ....................
-- . .
-- . Report . SPEC
-- . .
-- ....................
procedure Report
( Report File : in Text_IO.FILE_TYP );
-- Purpose
-- This procedure prints a report of all statistics for
-- this unit to the specified text file.
--
-- Exceptions (none)
--
-- Notes (none)
end Unit_Statistics
5.12.7.4 Example 12X4.
-- ***********************************
-- * *
-- * Telemetry_Reader_Statistics * SPEC
-- * *
-- ***********************************
package Telemetry_Reader Statistics is
new Unit_Statistics
( Unit_Name => "Telemetry_Reader",
ELEMENT_TYP => READER_ELEMENTS );
-- Purpose
-- This package collects statistics on elements of the
-- Telemetry_Reader.
--
-- Initialization Exceptions (none)
-- Notes (none)
5.13 Representation clauses and implementation-dependent features. Any
library units using representation clauses and implementation-dependent
features should explicitly document their use. One of the attractions of
Ada is its code reusability. If only machine independent interfaces are
presented, while representation clauses and implementation features are
hidden, those wishing to make use of the code may find unexplainable errors
arising.
5.13.1 Encapsulation. Representation clauses and implementation dependent
features should, if possible, be hidden inside packages which present
implementation independent interfaces to users.
5.13.2 Use of representation clauses and implementation-dependent features.
a. Machine dependent and low-level Ada features should not be used except
when absolutely necessary.
b. Representation clauses and implementation-dependent features should
only be used for one of the following:
(1) To increase efficiency (when absolutely necessary).
(2) For interrupt handling.
(3) For interfacing to hardware, foreign code or foreign data.
(4) To specify task storage size. Further, address clauses should be
used with entries only to associate them with hardware interrupts.
c. Representation clauses should not be used to change the meaning of a
program.
5.13.3 Interrupts. Interrupt routines should be kept as short as possible.
5.13.4 Formatting of representation clauses. Representation clauses should
be placed near the objects they affect. An exception may be to separate the
machine-independent from the machine-dependent representations.
5.14 lnput-output.
5.14.1 Encapsulation of I/O.
a. Use of the LOW_LEVEL_IO procedures should always be encapsulated in
packages or tasks.
b. Use of the LOW_LEVEL_IO procedures should generally be encapsulated in
task objects associated with each item of controlled equipment.
c. File management and textual input-output software should generally be
encapsulated in specialized packages with simple interfaces.
This should include file interface code, textual formatting code and user
inter-face code. User interface encapsulation can be especially useful when a
system must accommodate increasing levels of user interface sophistication or
changing user needs over its lifetime. In these cases it is crucial that
details of the implementation of the user interface be hidden so that changes
can be made to it without affecting the rest of the system.
NOTE: An argument can be made to encapsulate all I/O, to include the use
Text_IO and Direct_IO, because most Ada I/O has machine dependencies.
5.14.2 Text formatting. Line and page formatting should be done using the
New_Line and New_Page subprograms, rather than explicitly writing end-of-line
or end-of-page characters.
5.14.3 Low-level input-output. Use of package Low_Level-IO should be
avoided unless absolutely necessary.
5.14.4 Form parameter. Use of the Form parameter of the Open and Create
procedures should generally be avoided.
The "Form" parameter on the file Open and Create procedures specifies system
dependent file characteristics. This can reduce both readability and
portability, and so should only be used if absolutely necessary.
5.14.5 Examples for input-output. See also examples 5.5.9.3, 5.5.9.4 and
5.7.6.3.
5.14.5.1 Example 14X1.
-- ....................
-- . .
-- . Report . SUBUNIT
-- . .
-- ....................
separate (Unit_Statistics)
procedure Report
( Report_File : in Text_IO.FILE_TYP ) is
-- Notes
-- This example is based on Task_Statistics.Report
-- by Dan Roy.
--
-- Modifications
-- 8/18/86 Ed Seidewitz Initial version.
Unit_Name_Column
: constant := 10;
Value_Column
: constant := 40;
use Text_IO; -- For output operations.
begin -- Report
-- Print header
New_Line (Report File);
Set_Col (Report. File, To => Unit_Name_Column);
Put_line (Report_File,
"Statistics for " & STRING(Unit_Name)); New Line (Report_File);
Number_Lines_Printed := Number_Lines_Printed . 2;
-- Print "element name element value" for all elements
Print_All:
for Element in Statistics Array'range loop
Put (Report_File, ELEMENT_TYP'image (Element));
Set Col (Report_File, To => Value_Column);
Put Line (Report_File, INTEGER'image (Statistics_Array(Element)));
Number_Lines_Printed := Number_Lines_Printed + 1;
end loop Print_All;
end Report;
5.14.5.2 Example 14X2.
-- ....................
-- . .
-- . Read . SUBUNIT
-- . .
-- ....................
separate (Disk)
procedure Read
( Disk_File : in out FILE_TYP;
Data : out SPECIFIC_DATA_TYP ) is
-- Notes
-- (This is the body of procedure Read in example 7X3)
-- Modifications
-- 9/10/86 Ada User's Group Initial version
begin -- Read
if not Disk_IO.Is_Open(Disk_File.File) then
Open_File(Disk_File);
end if;
Disk_IO.Read
( File => Disk_File.File,
Item => Data );
exception
when Disk_IO.End Error =>
Disk_IO.Close (Disk_File.File);
raise End_Of_File;
when Disk_IO.Name Error Disk_IO.Use_Error =>
raise Open_Error;
when Disk_IO.Mode_Error =>
raise Mode_Error;
end Read;